Skip to content

Conversation

@RulaKhaled
Copy link
Member

@RulaKhaled RulaKhaled commented Nov 4, 2025

Fixes: https://linear.app/getsentry/issue/JS-1103/spans-are-not-flushed-to-dashboard-when-using-streamtext-with-vercel

The Cloudflare request wrapper was ending the root HTTP span immediately when the handler returned a streaming Response (e.g. result.toTextStreamResponse()). Since Vercel AI child spans only finish after the stream is consumed by the client, they were filtered out by Sentry's isFullFinishedSpan check, resulting in transactions with 0 spans.


This PR implements a streaming response detection and handles this from within the http handler:

  1. Created classifyResponseStreaming() helper

    • Detects streaming vs non streaming, via Content-Type (SSE), Content-Length
  2. Updated request wrapper

    • Changed from startSpan() to startSpanManual() for manual span control
    • Monitors streaming response consumption in background
    • Ends root span only after stream fully consumed by client

@linear
Copy link

linear bot commented Nov 4, 2025

@github-actions
Copy link
Contributor

github-actions bot commented Nov 4, 2025

size-limit report 📦

Path Size % Change Change
@sentry/browser 24.7 kB - -
@sentry/browser - with treeshaking flags 23.2 kB - -
@sentry/browser (incl. Tracing) 41.43 kB - -
@sentry/browser (incl. Tracing, Profiling) 45.75 kB - -
@sentry/browser (incl. Tracing, Replay) 79.85 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 69.57 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 84.55 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 96.77 kB - -
@sentry/browser (incl. Feedback) 41.38 kB - -
@sentry/browser (incl. sendFeedback) 29.39 kB - -
@sentry/browser (incl. FeedbackAsync) 34.33 kB - -
@sentry/react 26.41 kB - -
@sentry/react (incl. Tracing) 43.43 kB - -
@sentry/vue 29.15 kB - -
@sentry/vue (incl. Tracing) 43.23 kB - -
@sentry/svelte 24.72 kB - -
CDN Bundle 27.02 kB - -
CDN Bundle (incl. Tracing) 42.02 kB - -
CDN Bundle (incl. Tracing, Replay) 78.53 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 84.02 kB - -
CDN Bundle - uncompressed 79.17 kB - -
CDN Bundle (incl. Tracing) - uncompressed 124.55 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 240.59 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 253.35 kB - -
@sentry/nextjs (client) 45.84 kB - -
@sentry/sveltekit (client) 41.79 kB - -
@sentry/node-core 51.02 kB - -
@sentry/node 159.34 kB -0.01% -1 B 🔽
@sentry/node - without tracing 92.9 kB +0.01% +1 B 🔺
@sentry/aws-serverless 106.64 kB - -

View base workflow run

@github-actions
Copy link
Contributor

github-actions bot commented Nov 17, 2025

node-overhead report 🧳

Note: This is a synthetic benchmark with a minimal express app and does not necessarily reflect the real-world performance impact in an application.

Scenario Requests/s % of Baseline Prev. Requests/s Change %
GET Baseline 10,618 - 8,853 +20%
GET With Sentry 1,792 17% 1,777 +1%
GET With Sentry (error only) 6,867 65% 6,031 +14%
POST Baseline 1,061 - 1,207 -12%
POST With Sentry 472 44% 597 -21%
POST With Sentry (error only) 907 85% 1,060 -14%
MYSQL Baseline 3,575 - 3,301 +8%
MYSQL With Sentry 420 12% 490 -14%
MYSQL With Sentry (error only) 3,034 85% 2,724 +11%

View base workflow run

@RulaKhaled RulaKhaled requested review from AbhiPrasad and Lms24 and removed request for Lms24 November 21, 2025 12:49
@RulaKhaled RulaKhaled changed the title WIP fix(cloudflare): Keep http root span alive until streaming responses are consumed fix(cloudflare): Keep http root span alive until streaming responses are consumed Nov 21, 2025
@RulaKhaled RulaKhaled marked this pull request as ready for review November 21, 2025 12:50
Comment on lines +133 to +143
const result = await reader.read();
done = result.done;
}
} catch {
// Stream error or cancellation - will end span in finally
} finally {
reader.releaseLock();
span.end();
waitUntil?.(flush(2000));
}
})();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Missing error handling for response.body.tee() call.
Severity: MEDIUM | Confidence: Low

🔍 Detailed Analysis

The call to classification.response.body.tee() is not wrapped in error handling. If tee() throws (e.g., if the body stream is locked or in an invalid state), this will result in an uncaught exception. This bypasses existing error handling, preventing proper span termination and error capture by Sentry's exception handler.

💡 Suggested Fix

Wrap the classification.response.body.tee() call in a try-catch block to handle potential TypeError exceptions gracefully.

🤖 Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: packages/cloudflare/src/request.ts#L127-L143

Potential issue: The call to `classification.response.body.tee()` is not wrapped in
error handling. If `tee()` throws (e.g., if the body stream is locked or in an invalid
state), this will result in an uncaught exception. This bypasses existing error
handling, preventing proper span termination and error capture by Sentry's exception
handler.

Did we get this right? 👍 / 👎 to inform future reviews.
Reference ID: 2881816

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants